﻿/*
COPYRIGHT (C) 2009 EPISERVER AB

THIS FILE IS PART OF REMOTE PAGE PROVIDER FOR EPISERVER CMS.
REMOTE PAGE PROVIDER FOR EPISERVER CMS IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

REMOTE PAGE PROVIDER FOR EPISERVER CMS IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER GENERAL PUBLIC LICENSE FOR MORE DETAILS.
YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH REMOTE PAGE PROVIDER FOR EPISERVER CMS]. IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using EPiServer.DataAbstraction;
using EPiServer.Security;
using System.Collections.Specialized;
using System.Globalization;
using System.Net;
using System.Security.Principal;
using System.ServiceModel.Security;
using EPiServer.DataAccess;
using EPiServer.Events.Remote;
using EPiServer.Events.Clients;
using EPiServer.Events;
using System.IO;
using EPiServer.SpecializedProperties;
using EPiServer.Web.Hosting;
using EPiServer.Core;
using EPiServer.Web;
using System.Web;
using System.Configuration;

namespace EPiServer.Labs.RemoteProvider
{
    public partial class RemotePageProvider : PageProviderBase
    {
        int                         _remoteEntryPoint = -1;
        InitializeData              _initializeData;
        IRemoteProvider             _client;
        Guid                        _subscriberUniqueId   = Guid.Empty;
        Event                       _removeFromCacheEvent;
        string                      _remoteVirtualPath;
        // the Wastebasket page is not configuerd
        PageReference               _wasteBasketReference;
        bool                        _showContentOfWasteBasket = false;
        PageReference               _remoteEntrypPageReference;

        object _syncObject = new object();
        /// <summary>
        /// initialize flag
        /// </summary>
        bool                        _inited = false;

        #region Initialization

        /// <summary>
        /// Initializes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="config">The config.</param>
        public override void Initialize(string key, NameValueCollection config)
        {
            base.Initialize(key, config);

            _remoteVirtualPath = VirtualPathUtilityEx.ToAbsolute(base.Parameters.Get("remoteVirtualPath"));
            if (String.IsNullOrEmpty(_remoteVirtualPath))
            {
                throw new EPiServerException("The remoteVirtualPath configuration missing or empty attribute");
            }

            _remoteEntryPoint = Convert.ToInt32(base.Parameters["remoteEntryPoint"]);
            _remoteEntrypPageReference = new PageReference(_remoteEntryPoint, ProviderKey);
            _initializeData = new InitializeData();
            _initializeData.CapabilitySupported = base.Capabilities;

            CreateCredential();

            _client = new RemoteProviderClient(base.Parameters["endpointName"], base.Parameters.Get("proxyUserName"), base.Parameters.Get("proxyPassword"), _initializeData);
            _removeFromCacheEvent = Event.Get(CacheManager.RemoveFromCacheEventId);
            _removeFromCacheEvent.Raising += new EPiServer.Events.EventNotificationHandler(RemoveFromCacheEvent_Raising);
        }

        /// <summary>
        /// Initializes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="config">The config.</param>
        /// <param name="client">The client.</param>
        public void Initialize(string key, NameValueCollection config, IRemoteProvider client)
        {
            Initialize(key, config);
            if (client != null)
            {
                _client = client;
            }
        }
        #endregion

        private void Init()
        {
            if (!_inited)
            {
                lock (_syncObject)
                {
                    if (!_inited)
                    {
                        try
                        {
                            _client.Initialize(ref _initializeData);
                            HandleWasteBasket(_initializeData);
                            EnableRemoteEventsPublishing();
                            _inited = true;
                        }
                        catch (CommunicationException)
                        {
                        }
                    }
                }
            }
        }

        #region Implement Remote Page provider Base
        /// <summary>
        /// Should return references to all children of the specific page.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageID">The language ID.</param>
        /// <returns>Reference to all children</returns>
        /// <remarks>This is expected to return all children of the specified language</remarks>
        protected override PageReferenceCollection GetChildrenReferences(PageReference pageLink, string languageID)
        {
            // The pagelink is wastebasket which the server will not show in client side
            if (pageLink.ID == -1 && !_showContentOfWasteBasket)
            {
                return (PageReferenceCollection)null;
            }

            PageReference remotePageRef  = pageLink.CreateWritableClone();
            HandleRemoteEntryPoint(ref remotePageRef);
            PageReferenceCollection pageReferences = Client.GetChildrenReferences(remotePageRef);
            PageReferenceCollection tmpPageReferences = new PageReferenceCollection(pageReferences.Count);
            foreach (PageReference pageRef in pageReferences)
            {
                PageReference tmpPageRef = pageRef.CreateWritableClone();
                SetRemoteSiteName(tmpPageRef);
                tmpPageReferences.Add(tmpPageRef);
            }
            return tmpPageReferences;
        }
        /// <summary>
        /// Get collection of references to these pages
        /// </summary>
        /// <param name="pageLinks"></param>
        /// <returns></returns>
        public override PageReferenceCollection GetReferencesToPages(PageReferenceCollection pageLinks)
        {
            PageReferenceCollection writablePageLinks = new PageReferenceCollection();
            PageReference writablePageLink;
            foreach (PageReference pageLink in pageLinks)
            {
                writablePageLink = pageLink.CreateWritableClone();
                writablePageLinks.Add(writablePageLink);

                HandleRemoteEntryPoint(ref writablePageLink);
            }
            PageReferenceCollection pageReferences = Client.GetReferencesToPages(writablePageLinks);
            PageReferenceCollection tmpPageReferences = new PageReferenceCollection(pageReferences.Count);
            foreach (PageReference pageRef in pageReferences)
            {
                PageReference tmpPageRef = pageRef.CreateWritableClone();
                SetRemoteSiteName(tmpPageRef);
                tmpPageReferences.Add(tmpPageRef);
            }
            return tmpPageReferences;
        }

        /// <summary>
        /// Gets the local page.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="selector">The selector.</param>
        /// <returns></returns>
        protected override PageData GetLocalPage(PageReference pageLink, ILanguageSelector selector)
        {
            PageReference writeablePageLink = pageLink.CreateWritableClone();
            RemotePageRaw remotePageData = Client.GetPage(writeablePageLink, RemotePageHelper.GetLanguageSelector(writeablePageLink, selector));
            return CreatePageData(remotePageData, true);
        }

        /// <summary>
        /// Get all language branches for a page.
        /// The base class implemenation will first load the master page. Then look for existing languages in <see cref="PageData.PageLanguages"/>
        /// and load all languages.
        /// </summary>
        /// <param name="pageLink">The page to read.</param>
        /// <returns>
        /// A collection of pages that contains the found language branches.
        /// </returns>
        public override PageDataCollection GetLanguageBranches(PageReference pageLink)
        {
            return base.GetLanguageBranches(pageLink);
        }

        /// <summary>
        /// Create a page language in storage.
        /// Base class implentation will return a writable PageData object initialized for the specified language
        /// and the page type given by the master language version of the page.
        /// </summary>
        /// <param name="pageLink">The link to the page to create a language branch for.</param>
        /// <param name="selector">The language to create.</param>
        /// <returns>
        /// A PageData object with the newly created page data.
        /// </returns>
        public override PageData CreateLanguageBranch(PageReference pageLink, ILanguageSelector selector)
        {
            PageReference writeablePageLink = pageLink.CreateWritableClone();
            RemotePageRaw remotePageData = Client.CreateLanguageBranch(writeablePageLink, RemotePageHelper.GetLanguageSelector(writeablePageLink, selector));
            return CreatePageData(remotePageData, false);
        }

        /// <summary>
        /// Search for pages that fulfil specific criteria.
        /// If the provider is to be searchable this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">The root page for which the search will be performed</param>
        /// <param name="criterias">The criterias for pages</param>
        /// <param name="languageBranch">Only search for pages on specific language branch</param>
        /// <param name="selector">A language selector used to determine the language of pages to return</param>
        /// <returns>
        /// A collection of pages that fulfil the criteras, will be empty if no match is found.
        /// </returns>
        public override PageDataCollection FindPagesWithCriteria(PageReference pageLink, PropertyCriteriaCollection criterias, string languageBranch, ILanguageSelector selector)
        {
            PageReference writeablePageLink = pageLink.CreateWritableClone();
            RemotePageRaw[] remotePages = Client.FindPagesWithCriteria(writeablePageLink, criterias, languageBranch, RemotePageHelper.GetLanguageSelector(writeablePageLink, selector));
            return CreatePageDataCollection(remotePages, true);
        }

        /// <summary>
        /// Move a page to another container within the provider instance. Move between page provider instances
        /// is to be seen as a create at destination followed by a delete of source.
        /// If move is to be supported this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">The link to the page to move</param>
        /// <param name="destinationLink">The container to which the page will be moved</param>
        /// <remarks>
        /// A suggested implementation of move when destinationLink is <see cref="WastebasketReference"/> is to set
        /// <see cref="WastebasketReference"/> as parent and set property "PageDeleted" to true.
        /// </remarks>
        public override void Move(PageReference pageLink, PageReference destinationLink)
        {
            Client.Move(pageLink.CreateWritableClone(), destinationLink);  
        }

        /// <summary>
        /// Delete a language branch for a page.
        /// If delete is to be supported this method needs to be overriden
        /// </summary>
        /// <param name="pageLink">The link to the page which will be deleted</param>
        /// <param name="languageBranch">The language branch to delete</param>
        /// <remarks>You cannot delete the master language branch</remarks>
        public override void DeleteLanguageBranch(PageReference pageLink, string languageBranch)
        {
            Client.DeleteLanguageBranch(pageLink.CreateWritableClone(), languageBranch);
        }

        /// <summary>
        /// Delete a single version of a page.
        /// If versioning is to be supported this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">Link to the page and version which will be deleted</param>
        /// <remarks>The published version of page cannot be deleted</remarks>
        public override void DeleteVersion(PageReference pageLink)
        {
            Client.DeleteVersion(pageLink.CreateWritableClone());
        }

        /// <summary>
        /// Delete a page from storage.
        /// If delete is to be supported this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">The link to the page which will be deleted</param>
        /// <param name="forceDelete">Delete page even if being linked by other pages</param>
        public override void Delete(PageReference pageLink, bool forceDelete)
        {
            Client.Delete(pageLink.CreateWritableClone(), forceDelete);
        }

        /// <summary>
        /// Delete children of a page.
        /// If delete is to be supported this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">The children of this page will be deleted.</param>
        /// <param name="forceDelete">Delete page even if referenced by other pages.</param>
        public override void DeleteChildren(PageReference pageLink, bool forceDelete)
        {
            Client.DeleteChildren(pageLink.CreateWritableClone(), forceDelete);
        }

        /// <summary>
        /// Gets the reference to the waste basket for the page provider.
        /// if the  for the default provider and
        /// a PageReference with integer based id -1 and RemoteSite = ProviderKey
        /// </summary>
        /// <value>The waste basket.</value>
        public override PageReference WastebasketReference
        {
            get
            {
                Init();
                return   _wasteBasketReference;
            }
        }

        /// <summary>
        /// Move a page to the Wastebasket.
        /// Base class implementation calls Move(page, WastebasketReference).
        /// A suggested implementation is to set <see cref="WastebasketReference"/> as parent
        /// and set property "PageDeleted" to true.
        /// </summary>
        /// <param name="pageLink">The link to the page to move</param>
        /// <remarks>
        /// Moves a page from its current location into the Recycle Bin.
        /// This is the same as doing:
        /// <code>
        /// Move(pageLink, WasteBasket.PageLink);
        /// </code>
        /// 	<b>Note:</b>  You need Delete access to the page that is being moved.
        /// </remarks>
        public override void MoveToWastebasket(PageReference pageLink)
        {
            Client.MoveToWastebasket(pageLink.CreateWritableClone());
        }

        /// <summary>
        /// Copy a page to another container served by the same page provider.
        /// Copy between page provider instances is to be seen as a Create at destination provider.
        /// If copy is to be supported this method needs to be overriden.
        /// </summary>
        /// <param name="pageLink">The link to the page to copy.</param>
        /// <param name="destinationLink">The container where the page will be copied</param>
        /// <param name="publishOnDestination">If the new pages should be published on the destination</param>
        /// <param name="allowThreading">Run the local copy process on a separate thread (don't work for remote sites).</param>
        /// <returns></returns>
        /// <remarks>
        /// Note, when a page is copied successfully the <see cref="EPiServer.Core.PageStoreBase.CreatedPage"/> event is fired,
        /// since a page is created in the copy process. But in contrast to the Save method, that also
        /// fires the CreatedPage method and always passes an initialized PageData object in the
        /// <see cref="PageEventArgs"/> object to the event handler, the Copy method does not.
        /// Instead it only passes a pagereference to the copied page and it is up to the handler to
        /// load the page data. The reason for this behavior in the Copy method is performance.
        /// </remarks>
        public override PageReference Copy(PageReference pageLink, PageReference destinationLink, bool publishOnDestination, bool allowThreading)
        {
            PageReference copiedPageRef =  Client.Copy(pageLink.CreateWritableClone(), destinationLink, publishOnDestination, allowThreading);
            SetRemoteSiteName(copiedPageRef);
            return copiedPageRef;

        }

        /// <summary>
        /// Saves the specified page data.
        /// </summary>
        /// <param name="pageData">The page data.</param>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public override PageReference Save(PageData pageData, EPiServer.DataAccess.SaveAction action)
        {
            bool pageChanged = pageData.IsModified; // Saves the modify-flag as a temporary value, becouse this flag can be changed in the function.
            if (!PageReference.IsNullOrEmpty(pageData.PageLink))
            {
                pageData.PageLink.RemoteSite = String.Empty;
            }
            
            pageData.ParentLink.RemoteSite = String.Empty;
            pageData.LinkURL = String.Empty;
            if (pageData.ParentLink == EntryPoint)
            {
                pageData.ParentLink.ID = _remoteEntryPoint;
            }

            RemotePageRaw remotePageRaw = new RemotePageRaw();
            HandlePageDataLinks(pageData, true);
            remotePageRaw.Data = GetPropertyValues(pageData, PropertyTypes.LanguageSpecific | PropertyTypes.MetaData);
            remotePageRaw.Type = RemotePageHelper.PackPageDataType(pageData);
            PageReference savedPageRef = Client.Save(remotePageRaw, (int)action, pageChanged);
            SetRemoteSiteName(savedPageRef);

            return savedPageRef;
        }

        /// <summary>
        /// If the passed in identifier corresponds with an identifier for a page served by the page provider then the implementation should return
        /// the internal ("classic") link to the page and set PageReference identifier.
        /// The URI can be constructed by helper method <see cref="ConstructPageUri"/>.
        /// This typically maps to PageLink, PageGuid and LinkUrl properties for a PageData instance.
        /// </summary>
        /// <param name="pageGuid">The page GUID.</param>
        /// <param name="pageLink">The PageReference to the page with specified Guid based id</param>
        /// <returns>the "classic" Uri for the page.</returns>
        /// <remarks>
        /// Implementations of resolving methods must not call GetPage during resolving since that can cause infinite recursion.
        /// </remarks>
        /// <example>
        /// The following pseudo code shows how an implentation could look:
        /// <code>
        /// if (noMatch)
        /// {
        /// return null;
        /// }
        /// pageLink = &lt;the reference with ProviderKey set as RemoteSite&gt;
        /// return ConstructPageUri(pageTypeID, pageLink);
        /// </code>
        /// </example>
        protected override Uri ResolveLocalPage(Guid pageGuid, out PageReference pageLink)
        {
            pageLink = PageReference.EmptyReference;
            if (Client.GetCommunicationState() != CommunicationState.Opened)
            {
                return null;
            }
            string pageTypeName= string.Empty;
            pageTypeName = Client.ResolvePageFromPageGuid(pageGuid, out pageLink);
            pageLink = pageLink.CreateWritableClone();
            SetRemoteSiteName(pageLink);
            if (pageTypeName == string.Empty)
            {
                return null;
            }
            return MakeLocalUri(pageTypeName, pageLink).Uri;
        }

        /// <summary>
        /// If the passed in identifier corresponds with an identifier for a page served by the page provider then the implementation should return
        /// the internal ("classic") link to the page and set PageReference identifier.
        /// The URI can be constructed by helper method <see cref="ConstructPageUri"/>.
        /// This typically maps to PageLink, PageGuid and LinkUrl properties for a PageData instance.
        /// </summary>
        /// <param name="pageLink">The PageReference to the page</param>
        /// <param name="guid">The guid based identifier for the page</param>
        /// <returns>the "classic" Uri for the page.</returns>
        /// <remarks>
        /// Implementations of resolving methods must not call GetPage during resolving since that can cause infinite recursion.
        /// </remarks>
        /// <example>
        /// The following pseudo code shows how an implentation could look:
        /// <code>
        /// if (noMatch)
        /// {
        /// return null;
        /// }
        /// guid = &lt;the guid reference for the specified page&gt;
        /// pageLink = &lt;the reference with ProviderKey set as RemoteSite&gt;
        /// return ConstructPageUri(pageTypeID, pageLink);
        /// </code>
        /// </example>
        protected override Uri ResolveLocalPage(PageReference pageLink, out Guid guid)
        {
            guid = Guid.Empty; 
            if (Client.GetCommunicationState() != CommunicationState.Opened)
            {
                return null;
            }

            if (pageLink.RemoteSite != ProviderKey)
            {
                guid = Guid.Empty;
                return null;
            }
            string pageTypeName = string.Empty;
            pageTypeName = Client.ResolvePageFromPageLink(pageLink, out guid);
            if (pageTypeName == string.Empty)
            {
                return null;
            }
            return MakeLocalUri(pageTypeName, pageLink).Uri;
        }

        /// <summary>
        /// Lists all versions for a page for a specific language.
        /// Default base class implementation has no versioning support so it uses <see cref="GetPage"/> to get pages.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns>All matching versions</returns>
        public override PageVersionCollection ListVersions(PageReference pageLink, string languageBranch)
        {
            return Client.ListVersions(pageLink.CreateWritableClone(), languageBranch);
        }

        /// <summary>
        /// Lists all versions for a page.
        /// Default base class implementation has no versioning support so it uses <see cref="GetLanguageBranches"/> to get language pages.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns></returns>
        public override PageVersionCollection ListVersions(PageReference pageLink)
        {
            return Client.ListVersions(pageLink.CreateWritableClone(), string.Empty);
        }

        /// <summary>
        /// Lists the published versions
        /// Default base class implementation has no versioning support so it uses <see cref="GetLanguageBranches"/> to get pages.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns>All published versions for the page</returns>
        public override PageVersionCollection ListPublishedVersions(PageReference pageLink)
        {
            return Client.ListPublishedVersions(pageLink.CreateWritableClone());
        }

        /// <summary>
        /// Loads the specified version.
        /// Default base class implementation has no versioning support so it uses <see cref="GetPage"/> to get page.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns></returns>
        public override PageVersion LoadVersion(PageReference pageLink)
        {
            return Client.LoadVersion(pageLink.CreateWritableClone());
        }

        /// <summary>
        /// Loads the published version a page on the master language branch.
        /// Default base class implementation has no versioning support so it uses <see cref="GetPage"/> to get page.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns>
        /// PageVersion or null if page is not published
        /// </returns>
        /// <remarks>If the page is not published on the master language null is returned</remarks>
        public override PageVersion LoadPublishedVersion(PageReference pageLink)
        {
            return Client.LoadPublishedVersion(pageLink.CreateWritableClone(), string.Empty);
        }

        /// <summary>
        /// Loads the published version a page with specific language Branch.
        /// Default base class implementation has no versioning support so it uses <see cref="GetPage"/> to get page.
        /// If versioning is to be supported this method should be overriden.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns>
        /// PageVersion or null if page is not published on the specified language
        /// </returns>
        /// <remarks>If the page is not published on the specified language null is returned</remarks>
        public override PageVersion LoadPublishedVersion(PageReference pageLink, string languageBranch)
        {
            return Client.LoadPublishedVersion(pageLink.CreateWritableClone(), languageBranch);
        }

        /// <summary>
        /// Lists all pages that are set to delayed publish.
        /// Base class implementation does not support this method but returns new PageDataCollection().
        /// If delay publishing is to be supported this method should be overriden.
        /// </summary>
        /// <returns></returns>
        public override PageDataCollection ListDelayedPublish()
        {
            return (PageDataCollection)null;
        }

        #endregion

        #region Cache Event Handling
        /// <summary>
        /// Handles the Raising event of the RemoveFromCacheEvent control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EPiServer.Events.EventNotificationEventArgs"/> instance containing the event data.</param>
        void RemoveFromCacheEvent_Raising(object sender, EventNotificationEventArgs e)
        {
            if (_subscriberUniqueId == Guid.Empty)
            {
                return;
            }

            if ((e.RaiserId == _subscriberUniqueId) && (e.EventId == CacheManager.RemoveFromCacheEventId))
            {
                string cacheKey = e.Param.ToString() + "__" + base.ProviderKey;
                int pageRefID = 0;
                string cacheKeyPrefix = DataFactoryCache.ResolveCacheKey(e.Param.ToString(), out pageRefID);

                if (!string.IsNullOrEmpty(cacheKeyPrefix))
                {
                    if (pageRefID == _remoteEntryPoint)
                    {
                        cacheKey = cacheKeyPrefix + EntryPoint.ID;
                    }
                }
                e.Param = cacheKey;
            }
        }
        #endregion

        #region Helper Methods

        /// <summary>
        /// Creates the page data from RemotePageRaw.
        /// </summary>
        /// <param name="remotePageData">The remote page data.</param>
        /// <param name="readOnly">if set to <c>true</c> [read only].</param>
        /// <returns></returns>
        private PageData CreatePageData(RemotePageRaw remotePageData, bool readOnly)
        {

            PageData pageData = new PageData(new PageAccessControlList(), new PropertyDataCollection());

            PageReference pageLink;
            string pageTypeName = string.Empty;
            PageReference pageParentLink = PageReference.EmptyReference;

            GetPageInfo(remotePageData, out pageLink, out pageParentLink, out pageTypeName);
            if (pageParentLink == PageReference.EmptyReference)
            {
                throw new PageNotFoundException(new PageReference(pageLink.ID, ProviderKey));
            }

            base.InitializePageData(pageData, "", pageTypeName, Guid.Empty, pageLink, pageParentLink, remotePageData.Languages);
            SetACL(remotePageData, ref pageData);

            base.SetPropertyValues(pageData, remotePageData.Data,
                delegate(string propName, string porpType, string propValue)
                {
                    string type;
                    if (propName == "PageLink")
                    {
                        return false;
                    }

                    if (propName == "PageParentLink")
                    {
                        return false;
                    }
                    if (remotePageData.Type.TryGetValue(propName, out type))
                    {
                        return porpType.ToString() == type;
                    }
                    return false;
                }, false);

            HandlePageDataLinks(pageData, false);

            if (readOnly)
            {
                pageData.MakeReadOnly();
            }

            return pageData;
        }

        /// <summary>
        /// Creates the page data collection from array of RemotePageRaw .
        /// </summary>
        /// <param name="remotePagesData">The remote pages data.</param>
        /// <param name="readOnly">if set to <c>true</c> [read only].</param>
        /// <returns></returns>
        private PageDataCollection CreatePageDataCollection(RemotePageRaw[] remotePagesData, bool readOnly)
        {
            PageDataCollection pagesData = new PageDataCollection(remotePagesData.Length);
            foreach (RemotePageRaw remotePageData in remotePagesData)
            {
                pagesData.Add(CreatePageData(remotePageData, readOnly));
            }
            return pagesData;
        }

        /// <summary>
        /// Gets the page info such as PageLink, Page Parent Link and Page Type Name.
        /// </summary>
        /// <param name="remotePageData">The remote page data.</param>
        /// <param name="pageLink">The page link.</param>
        /// <param name="pageParentLink">The page parent link.</param>
        /// <param name="pageTypeName">Name of the page type.</param>
        private void GetPageInfo(RemotePageRaw remotePageData, out PageReference pageLink, out PageReference pageParentLink, out string pageTypeName)
        {
            pageLink = PageReference.EmptyReference;
            pageTypeName = string.Empty;
            pageParentLink = PageReference.EmptyReference;

            RemotePageHelper.GetProperty<PageReference>(remotePageData.Data, "PageLink", ref  pageLink,
                delegate(string value, ref PageReference tmpPageLink)
                {
                    tmpPageLink = PageReference.Parse(value);
                });

            RemotePageHelper.GetProperty<PageReference>(remotePageData.Data, "PageParentLink", ref pageParentLink,
                delegate(string value, ref PageReference tmpPageParentLink)
                {
                    tmpPageParentLink = PageReference.Parse(value);
                    HandlePageParentLink(ref tmpPageParentLink);
                });

            RemotePageHelper.GetProperty<string>(remotePageData.Data, "PageTypeName", ref pageTypeName,
                delegate(string value, ref string tmpPageName)
                {
                    tmpPageName = Convert.ToString(value, CultureInfo.InvariantCulture);
                });
        }

        /// <summary>
        /// Makes the local URI.
        /// </summary>
        /// <param name="pageTypeName">Name of the page type.</param>
        /// <param name="pageLink">The page link.</param>
        /// <returns></returns>
        private UrlBuilder MakeLocalUri(string pageTypeName, PageReference pageLink)
        {
            PageType pageType = PageType.Load(pageTypeName);
            UrlBuilder urlBuilder = new UrlBuilder(pageType.FileNameForSite(null));
            urlBuilder.QueryCollection.Add("id", pageLink.ToString());
            return urlBuilder;
        }

        /// <summary>
        /// Creates the credential.
        /// </summary>
        private void CreateCredential()
        {
            _initializeData.Credential = new ProviderCredentials();
            if (!string.IsNullOrEmpty(base.Parameters.Get("userName")))
            {
                _initializeData.Credential = new ProviderCredentials(base.Parameters.Get("userName"), base.Parameters.Get("password"));
            }
        }

        /// <summary>
        /// Sets the ACL.
        /// </summary>
        /// <param name="remotePageData">The remote page data.</param>
        /// <param name="pageData">The page data.</param>
        private void SetACL(RemotePageRaw remotePageData, ref PageData pageData)
        {
            if ((base.Capabilities & PageProviderCapabilities.Security) == PageProviderCapabilities.Security)
            {
                pageData.ACL = remotePageData.ACL;
            }
        }

        /// <summary>
        /// Enables the remote events publishing.
        /// </summary>
        private void EnableRemoteEventsPublishing()
        {
            _subscriberUniqueId = RemoteEventsManager.RegisterSite(_initializeData.RemoteSiteId, _initializeData.RemoteSiteSecret, RegistrationType.EventSubscriber);
        }

        /// <summary>
        /// Sets the name of the remote site.
        /// </summary>
        /// <param name="pageRef">The page ref.</param>
        private void SetRemoteSiteName(PageReference pageRef)
        {
            if (pageRef != PageReference.EmptyReference)
            {    
                pageRef.RemoteSite = ProviderKey;
            }
        }
        /// <summary>
        /// Handles the page parent link.
        /// </summary>
        /// <param name="pageParentLink">The page parent link.</param>
        private void HandlePageParentLink(ref PageReference pageParentLink)
        {
            if (pageParentLink.ID == _remoteEntryPoint)
            {
                pageParentLink = EntryPoint;
            }
            else
            {
                SetRemoteSiteName(pageParentLink);
            }
        }

        /// <summary>
        /// Handles the remote entry point.
        /// </summary>
        /// <param name="pageRef">The page ref.</param>
        private void HandleRemoteEntryPoint(ref PageReference pageRef)
        {
            if (pageRef.ID == EntryPoint.ID && String.IsNullOrEmpty(pageRef.RemoteSite))
            {
                pageRef.ID = _remoteEntryPoint;
            }

        }

        /// <summary>
        /// Handles the waste basket.
        /// </summary>
        /// <param name="_initializeData">The _initialize data.</param>
        private void HandleWasteBasket(InitializeData _initializeData)
        {
            if (!string.IsNullOrEmpty(base.Parameters.Get("showContentOfWasteBasket")) && Convert.ToBoolean(base.Parameters.Get("showContentOfWasteBasket")))
            {
                _wasteBasketReference = new PageReference(_initializeData.WasteBasketID, ProviderKey);
                _showContentOfWasteBasket = true;
            }
            else
            {
                _wasteBasketReference = new PageReference(-1, ProviderKey);
                _showContentOfWasteBasket = false;
            }
        }

        /// <summary>
        /// Handles the page data links.
        /// </summary>
        /// <param name="pageData">The page data.</param>
        /// <param name="fromLocalToRemote">if set to <c>true</c> [from local to remote].</param>
        private void HandlePageDataLinks(PageData pageData, bool fromLocalToRemote)
        {
            foreach (PropertyData prop in pageData.Property)
            {
                PropertyLongString longStringProp = prop as PropertyLongString;
                if (longStringProp != null)
                {
                    if (fromLocalToRemote)
                    {
                        string convertedLinks = prop.ReplaceHtmlLinks(prop.ToWebString(), LocalVirtualPathToRemoteVirtualPath);
                        prop.LoadData(convertedLinks);
                    }
                    else
                    {
                        string convertedLinks = prop.ReplaceHtmlLinks(prop.ToWebString(), RemoteVirtualPathToLocalVirtualPath);
                        prop.LoadData(convertedLinks);
                    }
                }
            }
            return;
        }

        /// <summary>
        /// Locals the virtual path to remote virtual path.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <returns></returns>
        private string LocalVirtualPathToRemoteVirtualPath(string link)
        {
            if (link.StartsWith(_remoteVirtualPath))
            {
                return link.Substring(_remoteVirtualPath.Length-1);
            }
            return link;
        }

        /// <summary>
        /// Remotes the virtual path to local virtual path.
        /// </summary>
        /// <param name="link">The link.</param>
        /// <returns></returns>
        private string RemoteVirtualPathToLocalVirtualPath(string link)
        {

            // Filter 
            if (String.IsNullOrEmpty(link) || link[0] != '/' || link.StartsWith(_remoteVirtualPath))
            {
                return link;
            }

            UrlBuilder url = new UrlBuilder(link);
            if (GenericHostingEnvironment.VirtualPathProvider.FileExists(Uri.UnescapeDataString(url.Path)))
            {
                return link;
            }

            return _remoteVirtualPath + link.Substring(1);
        }
        #endregion

        #region Property
        /// <summary>
        /// Gets the client.
        /// </summary>
        /// <value>The client.</value>
        public IRemoteProvider Client
        {
            get
            {
                Init();
                return _client;
            }
        }
        #endregion
    }
}
